﻿using System;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Controls;

namespace HeritageAcademy.Library.Controls
{
    public class Reveal : Decorator
    {
		#region Parameters 
        // Using a DependencyProperty as the backing store for AnimationProgress.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AnimationProgressProperty =
            DependencyProperty.Register(
                "AnimationProgress",
                typeof(double),
                typeof(Reveal),
                new FrameworkPropertyMetadata(
                    0.0,
                    FrameworkPropertyMetadataOptions.AffectsMeasure,
                    null,
                    OnCoerceAnimationProgress));
        // Using a DependencyProperty as the backing store for Duration.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(double), typeof(Reveal), new UIPropertyMetadata(250.0));
        // Using a DependencyProperty as the backing store for HorizontalReveal.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HorizontalRevealProperty =
            DependencyProperty.Register(
                "HorizontalReveal",
                typeof(HorizontalRevealMode),
                typeof(Reveal),
                new UIPropertyMetadata(HorizontalRevealMode.None));
        // Using a DependencyProperty as the backing store for IsExpanded.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsExpandedProperty =
            DependencyProperty.Register(
                "IsExpanded",
                typeof(bool),
                typeof(Reveal),
                new UIPropertyMetadata(false, OnIsExpandedChanged));
        // Using a DependencyProperty as the backing store for VerticalReveal.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty VerticalRevealProperty =
            DependencyProperty.Register(
                "VerticalReveal",
                typeof(VerticalRevealMode),
                typeof(Reveal),
                new UIPropertyMetadata(VerticalRevealMode.FromTopToBottom));
		#endregion

		#region Ctor 
        static Reveal()
        {
            ClipToBoundsProperty.OverrideMetadata(typeof(Reveal), new FrameworkPropertyMetadata(true));
        }
		#endregion

		#region Properties 
        /// <summary>
        ///     Value between 0 and 1 (inclusive) to move the reveal along.
        ///     This is not meant to be used with IsExpanded.
        /// </summary>
        public double AnimationProgress
        {
            get { return (double)GetValue(AnimationProgressProperty); }
            set { SetValue(AnimationProgressProperty, value); }
        }

        /// <summary>
        ///     The duration in milliseconds of the reveal animation.
        ///     Will apply to the next animation that occurs (not to currently running animations).
        /// </summary>
        public double Duration
        {
            get { return (double)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        public HorizontalRevealMode HorizontalReveal
        {
            get { return (HorizontalRevealMode)GetValue(HorizontalRevealProperty); }
            set { SetValue(HorizontalRevealProperty, value); }
        }

        /// <summary>
        ///     Whether the child is expanded or not.
        ///     Note that an animation may be in progress when the value changes.
        ///     This is not meant to be used with AnimationProgress and can overwrite any 
        ///     animation or values in that property.
        /// </summary>
        public bool IsExpanded
        {
            get { return (bool)GetValue(IsExpandedProperty); }
            set { SetValue(IsExpandedProperty, value); }
        }

        public VerticalRevealMode VerticalReveal
        {
            get { return (VerticalRevealMode)GetValue(VerticalRevealProperty); }
            set { SetValue(VerticalRevealProperty, value); }
        }
		#endregion

		#region Protected Methods 
        protected override Size ArrangeOverride(Size finalSize)
        {
            var child = Child;
            if (child == null)
                return new Size();

            var percent = AnimationProgress;
            var horizontalReveal = HorizontalReveal;
            var verticalReveal = VerticalReveal;

            var childWidth = child.DesiredSize.Width;
            var childHeight = child.DesiredSize.Height;
            var x = CalculateLeft(childWidth, percent, horizontalReveal);
            var y = CalculateTop(childHeight, percent, verticalReveal);

            child.Arrange(new Rect(x, y, childWidth, childHeight));

            childWidth = child.RenderSize.Width;
            childHeight = child.RenderSize.Height;
            var width = CalculateWidth(childWidth, percent, horizontalReveal);
            var height = CalculateHeight(childHeight, percent, verticalReveal);
            
            return new Size(width, height);
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            var child = Child;
            if (child == null)
                return new Size();

            child.Measure(availableSize);

            var percent = AnimationProgress;
            var width = CalculateWidth(child.DesiredSize.Width, percent, HorizontalReveal);
            var height = CalculateHeight(child.DesiredSize.Height, percent, VerticalReveal);
            
            return new Size(width, height);
        }
		#endregion 

		#region Private Methods 
        private static double CalculateHeight(double originalHeight, double percent, VerticalRevealMode reveal)
        {
            return reveal == VerticalRevealMode.None ? originalHeight : originalHeight * percent;
        }

        private static double CalculateLeft(double width, double percent, HorizontalRevealMode reveal)
        {
            switch (reveal)
            {
                case HorizontalRevealMode.FromRightToLeft:
                    return (percent - 1.0) * width;
                case HorizontalRevealMode.FromCenterToEdge:
                    return (percent - 1.0)*width*0.5;
                default:
                    return 0.0;
            }
        }

        private static double CalculateTop(double height, double percent, VerticalRevealMode reveal)
        {
            switch (reveal)
            {
                case VerticalRevealMode.FromBottomToTop:
                    return (percent - 1.0) * height;
                case VerticalRevealMode.FromCenterToEdge:
                    return (percent - 1.0) * height * 0.5;
                default:
                    return 0.0;
            }
        }

        private static double CalculateWidth(double originalWidth, double percent, HorizontalRevealMode reveal)
        {
            return reveal == HorizontalRevealMode.None ? originalWidth : originalWidth * percent;
        }

        private static object OnCoerceAnimationProgress(DependencyObject d, object baseValue)
        {
            var num = (double)baseValue;
            if (num < 0.0) return 0.0;
            return num > 1.0 ? 1.0 : baseValue;
        }

        private static void OnIsExpandedChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            ((Reveal)sender).SetupAnimation((bool)e.NewValue);
        }

        private void SetupAnimation(bool isExpanded)
        {
            // Adjust the time if the animation is already in progress
            var currentProgress = AnimationProgress;
            if (isExpanded)
                currentProgress = 1.0 - currentProgress;

            var animation = new DoubleAnimation
                                {
                                    To = isExpanded ? 1.0 : 0.0,
                                    Duration = TimeSpan.FromMilliseconds(Duration*currentProgress),
                                    FillBehavior = FillBehavior.HoldEnd
                                };

            BeginAnimation(AnimationProgressProperty, animation);
        }
		#endregion
    }

    public enum HorizontalRevealMode
    {
        /// <summary>
        ///     No horizontal reveal animation.
        /// </summary>
        None,

        /// <summary>
        ///     Reveal from the left to the right.
        /// </summary>
        FromLeftToRight,

        /// <summary>
        ///     Reveal from the right to the left.
        /// </summary>
        FromRightToLeft,

        /// <summary>
        ///     Reveal from the center to the bounding edge.
        /// </summary>
        FromCenterToEdge,
    }

    public enum VerticalRevealMode
    {
        /// <summary>
        ///     No vertical reveal animation.
        /// </summary>
        None,

        /// <summary>
        ///     Reveal from top to bottom.
        /// </summary>
        FromTopToBottom,

        /// <summary>
        ///     Reveal from bottom to top.
        /// </summary>
        FromBottomToTop,

        /// <summary>
        ///     Reveal from the center to the bounding edge.
        /// </summary>
        FromCenterToEdge,
    }
}
